package com.whjfxx.terminal.biz.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.whjfxx.terminal.biz.enums.SignType;
import com.whjfxx.terminal.biz.enums.YesNoEnum;
import com.whjfxx.terminal.biz.mapper.UserMapper;
import com.whjfxx.terminal.biz.param.*;
import com.whjfxx.terminal.biz.service.AccountService;
import com.whjfxx.terminal.biz.service.ResourceService;
import com.whjfxx.terminal.biz.service.UserService;
import com.whjfxx.terminal.biz.service.UserTokenService;
import com.whjfxx.terminal.biz.vo.LoginVo;
import com.whjfxx.terminal.biz.vo.MenuInfoVo;
import com.whjfxx.terminal.biz.vo.UserVo;
import com.whjfxx.terminal.commons.Content;
import com.whjfxx.terminal.entity.Account;
import com.whjfxx.terminal.entity.User;
import com.whjfxx.terminal.exceptions.BizException;
import com.whjfxx.terminal.utils.*;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * Created by b.c on 2022/4/12 14:55
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Autowired
    AccountService accountService;

    @Autowired
    UserTokenService userTokenService;

    @Autowired
    ResourceService resourceService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public LoginVo signIn(LoginParam loginParam) {
        validCaptcha(loginParam);
        if (SignType.password.name().equalsIgnoreCase(loginParam.getSignType())) {
            return this.signInPassword(loginParam.getAccountNo(), loginParam.getPassword(), loginParam.getSign());
        } else if (SignType.msg.name().equalsIgnoreCase(loginParam.getSignType())){
            return this.signVerificationCode(loginParam.getAccountNo(), loginParam.getVerificationCode(),loginParam.getSign());
        } else if (SignType.email.name().equalsIgnoreCase(loginParam.getSignType())) {
            return this.signVerificationCode(loginParam.getAccountNo(), loginParam.getVerificationCode(), loginParam.getSign());
        } else if (SignType.ic.name().equalsIgnoreCase(loginParam.getSignType())) {
            return this.signInIc(loginParam.getAccountNo(), loginParam.getSign());
        }
        throw BizException.build("登录认证类型不正确");
    }


    private void validCaptcha(LoginParam loginParam) {
        if (StringUtils.isBlank(loginParam.getGuid())){
            throw BizException.build("验证码无效");
        }
        String code = RedisUtil.get(loginParam.getGuid());
        if (Objects.isNull(code)) {
            throw BizException.build("验证码已过期");
        }

        if (!code.equalsIgnoreCase(loginParam.getCaptcha())) {
            throw BizException.build("验证码错误");
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void add(UserAddParam param) {
        if (SignType.password.name().equalsIgnoreCase(param.getType())) {
            User user = this.addUser(param);
            accountService.addPasswordUser(param.getAccountNo(), param.getPassword(), user);
        } else if (SignType.msg.name().equalsIgnoreCase(param.getType())){
            throw BizException.build("暂不支持");
        } else if (SignType.email.name().equalsIgnoreCase(param.getType())){
            throw BizException.build("暂不支持");
        } else if (SignType.ic.name().equalsIgnoreCase(param.getType())){
            throw BizException.build("暂不支持");
        } else {
            throw BizException.build("类型参数错误");
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void edit(UserEditParam param) {
        User user = this.getById(param.getId());
        if (Objects.isNull(user)) {
            throw BizException.build("用户不存在");
        }
        user.setNickName(param.getNickName());
        user.setSex(param.getSex());
        this.updateById(user);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateIco(Long userId, String icoUrl) {
        User user = this.getById(userId);
        if (Objects.isNull(user)) {
            throw BizException.build("用户信息不存在");
        }
        user.setIco(icoUrl);
        user.setUpdateTime(new Date());
        this.updateById(user);
    }

    @Override
    public Page<UserVo> pageList(SearchParam<UserPageParam> searchParam) {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper();
        QueryUtil.search(searchParam,UserPageParam.class, (pageParam) -> {
            if (StringUtils.isNotBlank(pageParam.getUserName())) {
                wrapper.like(User::getUserName, pageParam.getUserName());
            }
            if (StringUtils.isNotBlank(pageParam.getNickName())) {
                wrapper.like(User::getNickName, pageParam.getNickName());
            }
            if (StringUtils.isNotBlank(pageParam.getSex())) {
                wrapper.eq(User::getSex, pageParam.getSex());
            }
            if (StringUtils.isNotBlank(pageParam.getMobile())) {
                wrapper.like(User::getMobile, pageParam.getMobile());
            }
        });
        wrapper.eq(User::getDeleted, YesNoEnum.NO.status);
        this.baseMapper.selectPage(searchParam, wrapper);

        Page<User> page = PageUtils.buildPage(searchParam);
        if (CollectionUtils.isEmpty(page.getRecords())) {
            return PageUtils.convertEmpty(page);
        }

        // 查询账号
        List<Long> userIds = com.whjfxx.terminal.utils.BeanUtils.map(page.getRecords(), User::getId);
        List<Account> accounts = accountService.getPasswordUserByUserIds(userIds);
        Map<Long, Account> accountMap = accounts.stream().collect(Collectors.toMap(Account::getUserId, Function.identity(), BinaryOperatorUtil::after));

        Page<UserVo> pageVo = PageUtils.convert(page, UserVo.class, (src, target) -> {
            Account account = accountMap.get(src.getId());
            if (Objects.nonNull(account)) {
                target.setAccountNo(account.getAccountNo());
            }
        });
        return pageVo;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void setStatus(Long id, int status) {
        Objects.requireNonNull(id);
        Objects.requireNonNull(status);
        this.baseMapper.updateStatus(id, status);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteByIds(List<Long> ids) {
        Objects.requireNonNull(ids);
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper();
        wrapper.in(User::getId, ids);
        List<User> users = this.baseMapper.selectList(wrapper);
//        for (User user : users) {
//            user.setDeleted(YesNoEnum.YES.status);
//        }
//        this.updateBatchById(users);

        this.removeByIds(ids);
        // 删除所有account
        accountService.deleteByUserIds(ids);
    }

    @Override
    public void lockUser(Long userId) {
        RedisUtil.setIfAbsent(Content.RedisKeys.USER_LOGIN_LOCKED_PREFIX + userId, userId, 10 * 60);
    }

    @Override
    public void unLockUser(Long userId) {
        RedisUtil.delete(Content.RedisKeys.USER_LOGIN_LOCKED_PREFIX + userId);
    }

    /**
     * 添加用户信息
     * @param param
     * @return
     */
    private User addUser(UserAddParam param) {
        // 手机号码必填
        if (StringUtils.isBlank(param.getAccountNo())) {
            throw BizException.build("手机号码不能为空");
        }
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper();
        queryWrapper.eq(User::getMobile, param.getAccountNo());
        User user = this.baseMapper.selectOne(queryWrapper);
        if (Objects.nonNull(user)) {
            throw BizException.build("用户已经存在");
        }
        user = new User();
        if (StringUtils.isBlank(param.getUserName())) {
            user.setUserName(param.getAccountNo());
        } else {
            user.setUserName(param.getUserName());
        }
        if (StringUtils.isBlank(param.getNickName())) {
            user.setNickName(param.getAccountNo());
        } else {
            user.setNickName(param.getNickName());
        }
        user.setSex(param.getSex());
        user.setBirthday(param.getBirthday());
        user.setMobile(param.getAccountNo());
        user.setStatus(1);
        user.setSecret(UUID.randomUUID().toString().toLowerCase());
        user.setDeleted(YesNoEnum.NO.status);
        user.setCreateTime(new Date());
        user.setUpdateTime(new Date());
        this.save(user);
        return user;
    }

    /**
     * ic卡登录
     * @param accountNo
     * @param sign
     * @return
     */
    private LoginVo signInIc(String accountNo, String sign) {
        SignUtil.icSign(accountNo, sign);
        LambdaQueryWrapper<Account> wrapper = new LambdaQueryWrapper();
        wrapper.eq(Account::getAccountNo, accountNo);
        wrapper.eq(Account::getType, SignType.ic.name());
        Account account = accountService.getOne(wrapper);
        if (null == account) {
            throw BizException.build("登录失败");
        }

        // 查询用户信息
        User user = this.getById(account.getUserId());
        if (Objects.isNull(user)) {
            throw BizException.build("账号信息异常");
        }

        // 生成token
        String token = JWTUtil.generateToken(user);

        LoginVo loginVo = new LoginVo();
        BeanUtils.copyProperties(user, loginVo);
        loginVo.setToken(token);
        return loginVo;
    }

    /**
     * 验证码 登录
     * @param accountNo
     * @param verificationCode
     * @param sign
     * @return
     */
    private LoginVo signVerificationCode(String accountNo, String verificationCode, String sign) {
        SignUtil.verificationCodeSign(accountNo, verificationCode, sign);
        LoginVo loginVo = new LoginVo();
        return loginVo;
    }

    /**
     * 账号密码登录
     * @param accountNo
     * @param password
     * @param sign
     * @return
     */
    private LoginVo signInPassword(String accountNo, String password, String sign) {
        if (StringUtils.isBlank(accountNo)) {
            throw BizException.build("账号不能为空");
        }
        if (StringUtils.isBlank(password)) {
            throw BizException.build("密码不能为空");
        }
        SignUtil.passwordSign(accountNo, password, sign);
        LambdaQueryWrapper<Account> wrapper = new LambdaQueryWrapper();
        wrapper.eq(Account::getAccountNo, accountNo);
        wrapper.eq(Account::getPassword, password);
        wrapper.eq(Account::getType, SignType.password.name().toLowerCase());
        System.out.println("password:[ " + password + " ]");
        Account account = accountService.getOne(wrapper);
        if (Objects.isNull(account)) {
            throw BizException.build("用户名或密码错误");
        }
        if (Objects.isNull(account.getUserId())) {
            throw BizException.build("用户信息不全");
        }
        User user = this.getById(account.getUserId());
        if (Objects.isNull(user)) {
            throw BizException.build("用户信息不全");
        }
        String token = JWTUtil.generateToken(user);
        userTokenService.add(user.getId(), token);
        LoginVo loginVo = new LoginVo();
        loginVo.setUserName(user.getUserName());
        loginVo.setIco(user.getIco());
        loginVo.setUserId(user.getId());
        loginVo.setToken(token);

        // 根据用户id获取菜单列表
        MenuInfoVo menuInfoVo = resourceService.getMenuInfoVo(user);
        loginVo.setMenuInfo(menuInfoVo);
        return loginVo;
    }


    /**
     * 锁定账号
     * @param userId
     */
    private void lockedUser(Long userId) {
        String key = Content.RedisKeys.USER_LOGIN_ERROR_CNT_PREFIX + userId;
        Long incr = RedisUtil.incrIfAbsent(key, 5 * 60);
        if (incr > 5) {
            throw BizException.build("密码错误超过5次，用户已经被锁定");
        }
        RedisUtil.delete(key);
        // 锁住账号信息
    }

}
